package cas

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"golang.org/x/oauth2"
)

var (
	end, client      string
	orgName, appName string
	WhiteList        = []string{} // 白名单
)

// NewAuth 创建并初始化一个Auth实例，用于与Casdoor服务进行交互。
//
// 参数:
//
//	r: Gin的RouterGroup 实例，用于设置中间件和路由
//	endpoint: Casdoor服务的访问地址
//	clientID: 应用在Casdoor上的客户端ID
//	clientSecret: 应用在Casdoor上的客户端密钥
//	certificate: 用于加密的证书
//	organizationName: 组织名称
//	applicationName: 应用名称
//
// 返回值:
// - *Auth: 初始化后的Auth实例。
func InitCasdoor(endpoint, clientID, clientSecret, certificate, organizationName, applicationName string) {
	// 初始化配置，生成 globalClient 为与Casdoor服务的交互做准备。
	casdoorsdk.InitConfig(endpoint, clientID, clientSecret, certificate, organizationName, applicationName)
	end, client, orgName, appName = endpoint, clientID, organizationName, applicationName
}

// getCode 获取用户认证的code
//
// 参数:
//
//	userName: string 用户名
//	passWord: string 密码
//
// 返回值:
//
//	string - 认证的code
//	error - 错误信息，如果出现错误则返回
func getCode(userName, passWord string) (string, error) {
	// 构建登录URL
	// http://localhost/login/oauth/authorize?client_id=3593ed8d09016a88b227&response_type=code&redirect_uri=http://localhost:9000/callback&scope=read&state=casdoor
	url := fmt.Sprintf("%s/api/login?scope=read&state=%s&clientId=%s&responseType=code&redirectUri=http://localhost:9000/callback", end, appName, client)

	// 验证和清理输入
	if userName == "" || passWord == "" {
		return "", errors.New("用户名或密码为空")
	}

	// 序列化 JSON 数据并捕获错误
	jsonData := map[string]any{
		"username":     userName,
		"password":     passWord,
		"type":         "code",
		"application":  appName,
		"organization": orgName,
	}
	jsonStr, err := json.Marshal(jsonData)
	if err != nil {
		return "", fmt.Errorf("JSON 序列化失败: %w", err)
	}

	// 发送 HTTP POST 请求并捕获错误
	resp, err := http.Post(url, "application/json", bytes.NewReader(jsonStr))
	if err != nil {
		return "", fmt.Errorf("HTTP 请求失败: %w", err)
	}
	defer resp.Body.Close()

	// 读取响应体并捕获错误
	bs, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("读取响应体失败: %w", err)
	}

	// 反序列化 JSON 数据并捕获错误
	var mp map[string]any
	if err := json.Unmarshal(bs, &mp); err != nil {
		return "", fmt.Errorf("JSON 反序列化失败: %w", err)
	}

	// 安全地检查状态码
	status, ok := mp["status"].(string)
	if !ok || !strings.EqualFold(status, "OK") {
		msg, _ := mp["msg"].(string) // 忽略类型断言错误，因为已经知道不是 OK
		return "", fmt.Errorf("获取 code 失败: %s", msg)
	}

	// 获取 data 并返回
	code, ok := mp["data"].(string)
	if !ok {
		return "", errors.New("无效的 data 格式")
	}

	return code, nil
}

// Signin 处理用户登录逻辑，包括获取code和解析JWT令牌，并将用户信息保存到session中
//
// 参数:
//
//	userName - 用户名
//	passWord - 密码
//	ctx - Gin框架的上下文，用于管理session
//
// 返回值:
//
//	*casdoorsdk.Claims - 用户声明
//	error - 错误信息，如果出现错误则返回
func Signin(userName, passWord string) (*oauth2.Token, error) {
	// 获取授权码
	code, err := getCode(userName, passWord)
	if err != nil {
		return nil, errors.Wrap(err, "获取授权码失败")
	}

	// 生成随机状态值以防止CSRF攻击
	state, err := generateRandomState()
	if err != nil {
		return nil, errors.Wrap(err, "生成随机状态值失败")
	}

	// 使用授权码和状态请求Casdoor SDK以获取OAuth令牌
	return casdoorsdk.GetOAuthToken(code, state)
}

// Signout 处理用户退出逻辑，包括更新用户的在线状态和清除session
//
// 参数:
//
//	ctx - Gin框架的上下文，用于管理session
//
// 返回值:
//
//	error - 错误信息，如果出现错误则返回
func Signout(ctx *gin.Context) error {
	// 获取用户信息
	claims, err := GetClaims(ctx)
	if err != nil || claims == nil {
		return errors.New("用户未登录")
	}
	// headerToker := strings.Replace(ctx.GetHeader("Authorization"), "Bearer ", "", 1)
	// state, _ := generateRandomState()
	// url := fmt.Sprintf("%s/api/logout?id_token_hint=%s&state=%s&post_logout_redirect_uri=http://localhost:9000/callback", end, headerToker, state)
	url := fmt.Sprintf("%s/api/logout", end)
	// 发送 HTTP POST 请求并捕获错误
	resp, err := http.Post(url, "application/json", bytes.NewReader(nil))
	if err != nil {
		return fmt.Errorf("HTTP 请求失败: %w", err)
	}
	defer resp.Body.Close()

	// 读取响应体并捕获错误
	bs, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("读取响应体失败: %w", err)
	}

	// 反序列化 JSON 数据并捕获错误
	var mp map[string]any
	if err := json.Unmarshal(bs, &mp); err != nil {
		return fmt.Errorf("JSON 反序列化失败: %w", err)
	}
	if !strings.EqualFold(mp["status"].(string), "OK") {
		msg, _ := mp["msg"].(string) // 忽略类型断言错误，因为已经知道不是 OK
		return fmt.Errorf("获取 code 失败: %s", msg)
	}
	return nil
}

// 辅助函数：生成随机状态值
func generateRandomState() (string, error) {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(b), nil
}

// GetClaims 检查session中是否存在用户的登录信息
//
// 参数:
//
//	ctx - Gin框架的上下文，用于管理session
//
// 返回值:
//
//	*casdoorsdk.Claims - 用户声明
func GetClaims(ctx *gin.Context) (*casdoorsdk.Claims, error) {
	// 避免同一台设备登录多个账号
	headerToker := strings.Replace(ctx.GetHeader("Authorization"), "Bearer ", "", 1) // apiFox 会增加 Bearer
	return casdoorsdk.ParseJwtToken(headerToker)
}

// ChangePwd 修改用户的密码。
//
// 参数:
//
//	userName - 用户名
//	oldPassword - 旧密码
//	newPassword - 新密码
//
// 返回值:
//
//	error - 错误信息，如果出现错误则返回
func ChangePwd(userName, oldPassword, newPassword string) error {
	// 验证和清理输入
	if userName == "" || oldPassword == "" || newPassword == "" {
		return errors.New("用户名、旧密码或新密码为空")
	}

	// 使用casdoorsdk.SetPassword修改密码
	_, err := casdoorsdk.SetPassword(orgName, userName, oldPassword, newPassword)
	return err
}
